package models;

// File: CustomTable.java

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*; 
import java.text.*; 
import javax.swing.event.EventListenerList;

import system.ExceptionEvent;
import system.ExceptionListener;


// 	CustomTable has the functionality of both a model and a view. Consider
//	splitting it
@SuppressWarnings("serial")
public class 
CustomTable 
extends JTable
implements ExceptionObserver
{	protected CustomTableModel model = null;
    protected EventListenerList listener_list = new EventListenerList();

    public 
    CustomTable(CustomTableModel m)
    {	super(m);

		// dataModel is a protected member of JTable
	    model = (CustomTableModel)dataModel;
	
		initColumnSizes();	
	
		// this prevents column widths from shrinking when
		// there are many columns
		setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
	
		// The CustomTableModel inner class Sorter sorts a column in
		// ascending order when the column's header is double clicked. 
		// If the shift key is held down during the clicks, the 
		// column will be sorted in descending order. 
		(model.new Sorter(this)).addMouseListenerToHeader();
	
		// Inform user that the table columns can be sorted
		// if the table model is not updatable
		if (!model.isCellEditable(0,0))
		    getTableHeader().setToolTipText("Double click to sort");
	
		CellMouseListener cellListener = new CellMouseListener(this);
		addMouseMotionListener(cellListener);
	
		// There are no default renderers and editors for Date, Timestamp, and Time
		// so we need to create them. I will only create the renderer and editor for
		// Date. You need to create the rest.
		// You can ignore this if your database table doesn't deal with these 
		// types.
		setDefaultRenderer(java.sql.Date.class, new DefaultTableCellRenderer());
		setDefaultEditor(java.sql.Date.class, new DateCellEditor());
    }

    private void 
    initColumnSizes() 
    {	TableColumn column = null;

		// comp is the component used to render a cell or cell header.
		// We will compare the preferred width of the widest cell
		// in each column to the preferred width of the column's
		// header. The column width will be the maximum of the two.
	    Component comp = null;
	
		int numColumns = getColumnCount();
		int numRows = getRowCount();
		int headerWidth = 0;
	
		// cellWidth will store the width of the widest cell 
		// in each column
		int cellWidth = 0;
	
		int temp = 0;
	
		// this is not the most efficient way of finding the widest 
		// cell in each column
        for (int i = 0; i < numColumns; i++) 
        {	column = getColumnModel().getColumn(i);

		    comp = getTableHeader().getDefaultRenderer().
			getTableCellRendererComponent(this, column.getHeaderValue(), 
						      false, false, 0, i);
	                           
		    headerWidth = comp.getPreferredSize().width; 
	         
		    for (int j = 0; j < numRows; j++)
		    {	comp = getDefaultRenderer(model.getColumnClass(i)).
			    	getTableCellRendererComponent(this, getValueAt(j,i),
							  false, false, j, i);
	
				temp = comp.getPreferredSize().width;
		
				if (temp > cellWidth) cellWidth = temp;
		    }
	
		    // the 20 is for extra padding
	            column.setPreferredWidth(Math.max(headerWidth+20, cellWidth+20));
		    cellWidth = 0;
        }
    }
    
    // called by the getCellEditorValue() method of the DateCellEditor;
    // used to notify associated controller that an exception has occurred
    private void 
    fireExceptionGenerated(ExceptionEvent ex) 
    {	// Guaranteed to return a non-null array
    	Object[] listeners = listener_list.getListenerList();

    	// Process the listeners last to first, notifying
    	// those that are interested in this event.
    	// I have no idea why the for loop counts backwards by 2
    	// and the array indices are the way they are.
    	for (int i = listeners.length-2; i>=0; i-=2) 
    		if (listeners[i]==ExceptionListener.class) 
    			((ExceptionListener)listeners[i+1]).exceptionGenerated(ex);
     }

    /**************************************************************************
     * CellMouseListener Inner Class
     * This class allows a cell's data to be displayed over the mouse cursor 
     * when the mouse cursor is over the selected cell. Note: to actually have
     * the data displayed, the mouse cursor must first move a little bit in the
     * selected cell.
     *************************************************************************/ 
    class 
    CellMouseListener 
    extends MouseMotionAdapter
    {	private int row = -1;
		private int col = -1; 
		private Object oldValue = null; 
		private JTable table = null;
	
		public 
		CellMouseListener(JTable table)
		{	this.table = table; }
	
	
		public void 
		mouseMoved(MouseEvent event)
		{	Point p = event.getPoint();
		    int tempRow = table.rowAtPoint(p);
		    int tempCol = table.columnAtPoint(p);	   	   
	
		    if (tempRow == -1 || tempCol == -1)
		    	return;
		
		    // return if the cell is not selected 
		    if (!table.getSelectionModel().isSelectedIndex(tempRow) 
		    	|| !table.getColumnModel().getSelectionModel()
		    	.isSelectedIndex(tempCol)
		    ) {
				// Remove the old tool tip from the previous cell selection (if there is one).
				// Don't remove if the mouse pointer is still in the same cell.
				if ( (row != -1 && col != -1) && (row != tempRow || col != tempCol) )
				{    ((JComponent)(table.getCellRenderer(row, col).getTableCellRendererComponent
						  (table, null, false, false, row, col))).setToolTipText(null);
				    // set row and col to -1 so we don't end up later removing the tool tip again
				    row = -1;
				    col = -1; 
				}
				return; 
		    }
	
		    Object obj = table.getValueAt(tempRow, tempCol);
	
		    // return if the mouse pointer is in the same cell and the value in the cell 
		    // has not changed
		    if (row == tempRow && col == tempCol)
		    {	if ((obj == null && oldValue == null) 
		    		|| (obj != null && obj.equals(oldValue)))		  
			    	return; 
	
			    // only when tempRow, tempCol, obj represent a valid cell whose value is 
			    // different from its old value can we set the values for row, col, and oldValue
			    row = tempRow;
			    col = tempCol;
			    oldValue = obj; 
		
			    // if obj is null or the string value of obj is the empty string, 
			    // we will not display any tool tip text
			    if (obj != null)
			    {	String value = obj.toString();
				
					if (value.length() != 0)
					    ((JComponent)(table.getCellRenderer(row,col).
					    	getTableCellRendererComponent(
					    		table, null, false, false, row, col
					    	))).setToolTipText(value);
					else((JComponent)(table.getCellRenderer(row,col)
					    	.getTableCellRendererComponent(
					    		table, null, false, false, row, col
					    	))).setToolTipText(null);
				}  else ((JComponent)(table.getCellRenderer(row,col)
				    		.getTableCellRendererComponent(
				    			table, null, false, false, row, col
				    		))).setToolTipText(null);
			}
		}
    }

    /***************************************************************************
     * Cell editor for java.sql.Date; limited error checking is performed;
     * Dates must be in JDBC format
     **************************************************************************/ 
    
    class 
    DateCellEditor 
    extends DefaultCellEditor
    {	JTextField textField;

		public 
		DateCellEditor()
		{   super(new JTextField());
		    // editorComponent is a protected member of DefaultCellEditor
		    textField = (JTextField)editorComponent; 
		}
	
	
		// the editor must return a Date object
		public Object 
		getCellEditorValue()
		{   try
		    {	if (textField.getText().trim().length() == 0)
			    	return null; 
	
				SimpleDateFormat fm = new SimpleDateFormat("yyyy-MM-dd");
				java.util.Date utilDate = fm.parse(textField.getText().trim());
				java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());
				
				return sqlDate; 
			} catch (ParseException e)
			{	ExceptionEvent event = new ExceptionEvent(
					this, "Invalid Date Format. Date must be in form yyyy-MM-dd"
				);
				fireExceptionGenerated(event);
		
				// this line will automatically generate an exception in CustomTableModel
				return textField.getText(); 
			}
		}
    }
    
    /***************************************************************************
     * ExceptionObserver Interface Methods
     **************************************************************************/ 
    
    public void 
    addExceptionListener(ExceptionListener l) 
    { listener_list.add(ExceptionListener.class, l); }


    public void 
    removeExceptionListener(ExceptionListener l) 
    { listener_list.remove(ExceptionListener.class, l); }
}
